/*
 * Value.java - Created on Thu Dec 23 11:08:02 EST 2004
 *
 * Copyright (C) 2004 by Christian Jean.
 * All rights reserved.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION!
 *
 * Disclosure or use in part or in whole without prior written consent
 * constitutes an infringement of copyright laws which may be punishable
 * by law.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES
 * INCLUDING,  BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND  FITNESS FOR A PARTICULAR  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * THE AUTHOR(S), COPYRIGHT  HOLDER(S)  OR ITS  CONTRIBUTOR(S) BE LIABLE FOR
 * ANY DIRECT,  INDIRECT,  INCIDENTAL, SPECIAL, EXEMPLARY,  OR CONSEQUENTIAL
 * DAMAGES  (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES;  LOSS OF USE, DATA, OR  PROFITS;  OR BUSINESS  INTERRUPTION)
 * HOWEVER  CAUSED  AND  ON  ANY  THEORY  OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY, OR TORT (INCLUDING  NEGLIGENCE  OR OTHERWISE)  ARISING
 * IN  ANY WAY  OUT  OF THE  USE OF  THIS SOFTWARE,  EVEN IF  ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.jeach.stats;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.log4j.Logger;


/**
 * The <code>Value</code> class represents a single numerical value that when
 * combined with others allows us to create a data set. It implements the
 * <code>Number</code> interface for full Java support. Of importance, it can
 * have one or more correlations attached to it for later referential purposes.
 * <p>
 * Correlation information will be used when comparing value instances. The
 * primary reason for this is that any object may be added and used as a
 * correlation and they are not always comparable to other types of objects
 * bound to a correlated name.
 * <p>
 * For example:
 * <p>
 * Binding a correlation of type <tt>Double</tt> using the name 'my.data' to a
 * first value <tt>v1</tt> and binding a correlation to fyp <tt>String</tt>
 * using the same name 'my.data' to a second value <tt>v2</tt>. Trying to
 * compare these two values would be impossible since they are not of the same
 * type.
 * <p>
 * The value assigned is immutable for an instance. Only correlations and the
 * name may be changed.
 * 
 * @author Christian Jean (dev@jeach.com)
 * @since 1.0
 */
public class Value extends Number implements Comparable {

   /** Log4J library */
   private static final Logger log = Logger.getLogger(Value.class);

   private double value = 0.0d;
   private String name = null;
   private Map correlations = null;

   /**
    * Constructs a default instance which will be initialized to a value of zero
    * (0.0d).
    */
   public Value() {
      this(0.0d);
   }

   /**
    * Constructs an instance by specifiying its initial value.
    * 
    * @param value to be initialized to
    */
   public Value(double value) {
      setValue(value);
   }

   /**
    * Constructs an instance by specifiying its initial value.
    * 
    * @param value to be initialized to
    */
   public Value(long value) {
      setValue(value);
   }

   /**
    * Constructs an instance by specifiying its initial value.
    * 
    * @param value to be initialized to
    */
   public Value(Number value) {
      setValue(value);
   }

   /**
    * Constructs an instance by specifiying its initial value.
    * 
    * @param value to be initialized to
    */
   public Value(Value value) {
      setValue(value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(Number value) {
      setValue(value.doubleValue());
   }

   /**
    * Set the value and name of this instance and copy all correlations.
    * <p>
    * TODO: Make sure that copying the correlations work!
    * 
    * @param value to be set to
    */
   private void setValue(Value value) {
      setValue(value.getValue());
      setName(value.getName());
      addCorrelations(value.getCorrelations());
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(float value) {
      setValue((double) value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(int value) {
      setValue((double) value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(long value) {
      setValue((double) value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(short value) {
      setValue((double) value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private void setValue(char value) {
      setValue((double) value);
   }

   /**
    * Set the value of this instance.
    * 
    * @param value to be set to
    */
   private synchronized void setValue(double value) {
      log.debug("Set value: " + value);
      this.value = value;
   }

   /**
    * Returns the set value for this instance.
    * 
    * @return a double value
    */
   public synchronized double getValue() {
      return value;
   }

   /**
    * Sets the name for this instance.
    * 
    * @param name to set for this instance
    */
   public synchronized void setName(String name) {
      log.debug("Set name: " + name);
      this.name = name;
   }

   /**
    * Retrieves the name of this instance.
    * 
    * @return the name of this instance
    */
   public synchronized String getName() {
      return name; // != null ? name : "Value:" + hashCode();
   }

   // ///////////////////////////////////////////////////////////////////////
   // Copy Of Value Section
   // ///////////////////////////////////////////////////////////////////////

   /**
    * Creates a new instance of value using the current value, name and
    * correlation mappings.
    * 
    * @return a <tt>Value</tt> instance
    */
   public synchronized Value getCopy() {
      return getCopy(doubleValue());
   }

   /**
    * Creates a new instance of value using the current name and correlation
    * mappings.
    * 
    * @param value new value of the copy
    * @return a <tt>Value</tt> instance
    */
   public synchronized Value getCopy(Number value) {
      return getCopy(value.doubleValue());
   }

   /**
    * Creates a new instance of value using the current name and correlation
    * mappings.
    * 
    * @param value new value of the copy
    * @return a <tt>Value</tt> instance
    */
   public synchronized Value getCopy(long value) {
      return getCopy((double) value);
   }

   /**
    * Creates a new instance of value using the current name and correlation
    * mappings.
    * 
    * @param value new value of the copy
    * @return a <tt>Value</tt> instance
    */
   public synchronized Value getCopy(double value) {
      Value v = new Value(value);

      v.name = name;

      if (correlations != null) {
         v.correlations = new HashMap(correlations);
      }

      return v;
   }

   // ///////////////////////////////////////////////////////////////////////
   // Correlation Section
   // ///////////////////////////////////////////////////////////////////////

   /**
    * Maps a nameless correlation to this dataset value. Since a correlation
    * name is not provided, one will be createed interanally.
    * 
    * @param data is an object to be correlated to this value
    */
   public void addCorrelation(Object data) {
      addCorrelation(null, data);
   }

   /**
    * Maps a correlation name/object pair to this dataset value.
    * 
    * @param data is an object to be correlated to this value
    */
   public synchronized void addCorrelation(String name, Object data) {
      String key = null;

      log.debug("Add correlation ...");
      log.debug("-> Name: " + name);
      log.debug("-> Data: " + data);

      if (data != null) {
         if (correlations == null) {
            log.debug("Allocating map!");
            correlations = new HashMap();
         }

         key = name != null ? name : "ds.correlation:" + System.nanoTime();

         correlations.put(key, data);
      }
   }

   /**
    * Maps each object of the collection as a nameless correlation to this
    * dataset value. Since a correlation name is not specified, one will be
    * created internally.
    * <p>
    * <b>Note</b>: This will not add the collection object as a correlation,
    * but rather its contents.
    * 
    * @param correlations is a collection of correlations to add to this value
    */
   public void addCorrelations(Collection correlations) {
      Iterator it = null;

      if (correlations != null) {
         it = correlations.iterator();
      }

      if (it != null) {
         while (it.hasNext()) {
            addCorrelation(it.next());
         }
      }
   }

   /**
    * Removes all objects correlated to this dataset value. There will be no
    * more correlations available.
    */
   public synchronized void removeCorrelations() {
      if (correlations != null) {
         correlations.clear();
         correlations = null;
      }
   }

   /**
    * Removes the correlated object from the collection of correlations. If the
    * same object is correlated more than once, all correlations will be
    * removed.
    * 
    * @param data object to be removed
    * @return true if found and removed, false otherwise
    */
   public synchronized boolean removeCorrelation(Object data) {
      boolean removed = false;

      if (data != null) {
         if (correlations != null) {
            Iterator it = null;

            if (correlations.containsValue(data)) {
               it = correlations.values().iterator();
            }

            if (it != null) {
               Object o = null;

               while (it.hasNext()) {
                  o = it.next();

                  if (data.equals(o)) {
                     it.remove();
                  }
               }
            }
         }
      }

      return removed;
   }

   /**
    * Counts the number of correlated objects.
    * <p>
    * TODO: Refactor this to getCorrelationCount()
    * 
    * @return a count
    */
   public synchronized int getCorrelationCount() {
      return correlations != null ? correlations.size() : 0;
   }

   /**
    * Retrieves the correlation mapped to the specified name.
    * 
    * @param name of the correlation
    * @return an object
    */
   public synchronized Object getCorrelation(String name) {
      return correlations != null ? correlations.get(name) : null;
   }

   /**
    * Retrieve all associated correlations which are attached to this dataset
    * value.
    * <p>
    * The correlation 'keys' or names are not provided, only the correlated
    * objects.
    * 
    * @return a collection of correlated objects
    * @see HashMap#values for more details on key/value mapping details
    */
   public synchronized Collection getCorrelations() {
      return correlations != null ? correlations.values() : null;
   }

   /**
    * Provides the key mappings (or names) for the collection of correlations.
    * 
    * @return a collection of names
    * @see HashMap#keySet for more details on key/value mapping details
    */
   public synchronized Collection getCorrelationKeys() {
      return correlations != null ? correlations.keySet() : null;
   }

   // ///////////////////////////////////////////////////////////////////////
   // Number Implementation
   // ///////////////////////////////////////////////////////////////////////

   /**
    * Returns the value of the specified number as an <code>int</code>. This
    * may involve rounding or truncation.
    * 
    * @return the numeric value represented by this object after conversion to
    *         type <code>int</code>.
    */
   public int intValue() {
      return (new Double(value).intValue());
   }

   /**
    * Returns the value of the specified number as a <code>long</code>. This
    * may involve rounding or truncation.
    * 
    * @return the numeric value represented by this object after conversion to
    *         type <code>long</code>.
    */
   public long longValue() {
      return (new Double(value).longValue());
   }

   /**
    * Returns the value of the specified number as a <code>float</code>. This
    * may involve rounding.
    * 
    * @return the numeric value represented by this object after conversion to
    *         type <code>float</code>.
    */
   public float floatValue() {
      return (new Double(value).floatValue());
   }

   /**
    * Returns the value of the specified number as a <code>double</code>.
    * This may involve rounding.
    * 
    * @return the numeric value represented by this object after conversion to
    *         type <code>double</code>.
    */
   public double doubleValue() {
      return value;
   }

   // ///////////////////////////////////////////////////////////////////////////
   // Support methods...
   // ///////////////////////////////////////////////////////////////////////////

   /**
    * Compares the specified object with this value for equality. Returns
    * <tt>true</tt> if the given object is also a value and the two values
    * represent the same value.
    * 
    * @param o object to be compared for equality with this value.
    * @return <tt>true</tt> if the specified object is equal to this value.
    */
   public boolean equals(Object o) {
      if (!(o instanceof Value)) { return false; }

      if (o == this) { return true; }

      Value v = (Value) o;

      return new EqualsBuilder().append(value, v.value).append(name, v.name).isEquals();
      // return new EqualsBuilder().appendSuper(super.equals(o)).append(value,
      // v.value).append(name, v.name).isEquals();
      // return new EqualsBuilder().appendSuper(super.equals(o)).append(value,
      // v.value).append(name, v.name).append(correlations,
      // v.correlations).isEquals();
   }

   /**
    * Returns the hash code value for this value.
    * 
    * @return the hash code value for this value.
    * @see Object#hashCode()
    * @see Object#equals(Object)
    * @see #equals(Object)
    */
   public int hashCode() {
      // Pick a hard-coded, randomly chosen, non-zero, odd number
      // ideally different for each class
      return new HashCodeBuilder(15465163, 51656547).append(super.hashCode()).append(value).append(name).append(correlations).toHashCode();
   }

   /**
    * Allows comparission between object instances.
    * 
    * @param o is the object instance to be compared to
    * @return true if they are the same, false otherwise
    */
   public int compareTo(Object o) {
      Value x = (Value) o;

      log.debug("Comparing this : " + value);
      log.debug("Comparing to   : " + x.value);

      return new CompareToBuilder().append(value, x.value).toComparison();
   }

   /**
    * Returns a string representation of the object. In general, the
    * <code>toString</code> method returns a string that 'textually
    * represents' this object. The result should be a concise but informative
    * representation that is easy for a person to read. It is recommended that
    * all subclasses override this method.
    * 
    * @return a string representation of the object.
    * @author Christian Jean (wasp@jeach.com)
    * @since 1.0.0.1, ICR-12342, Sun Jan 30 14:10:56 EST 2005
    */
   public String toString() {
      return "" + value;
      // ToString buf = new ToString(this);
      //
      // buf.add("Value", getValue());
      // buf.add("Name", getName());
      // buf.add("Count", this.getCorrelationCount());
      // buf.add("Correlations", correlations);
      //
      // return (buf.toString());
   }
}
